package plp.prolog.declaration;

import java.util.ArrayList;

import plp.prolog.memory.AmbienteExecucaoProlog;

public class Fato implements Expressao{
	private Predicado predicado;
	private ArrayList<Termo> args;

	public Fato(Predicado predicado, ArrayList<Termo> args) {
		this.predicado = predicado;
		this.args = args;
	}

	@Override
	public boolean avaliar(AmbienteExecucaoProlog ambienteExecucao){
		ArrayList<Fato> derivacoes = ambienteExecucao.encontrarDerivacoes(this);
		boolean resultado = !derivacoes.isEmpty();
		
		for(int index = 0; index < derivacoes.size() && resultado; index++){
			Fato fato = derivacoes.get(index);

			ambienteExecucao.incrementa();
			resultado = ambienteExecucao.resolverUnificacoes(this, fato);

			if( resultado ){
				ambienteExecucao.printUnificacoes();
			}else{
				System.out.println(this + " NAO UNIFICOU COM " + fato) ;
			}

			ambienteExecucao.restaura();
		}

		return resultado;
	}

	//	private final boolean executarBacktracking(ArrayList<No> derivacoes, ResultadoDerivacao resultado, int index) {
	//		if (!isLastDerivation(derivacoes, index)) { // nao eh ultima derivacao
	//		    return requestBacktracking();
	//		} else {
	//			Utils.imprimir(resultado.getResultado() ? "SIM" : "NAO");
	//		}
	//		return false;
	//		
	//	}

	//	private boolean requestBacktracking() {
	//		String backtracking = Utils.ler();
	//		if (backtracking.compareTo(Constants.SEMICOLON) == 0) {
	//			return true;
	//		}
	//		
	//		return false;
	//	}

	//	private final boolean isLastDerivation(ArrayList<No> derivacoes, int index) {
	//		return index < derivacoes.size() - 1;
	//	}

	public boolean combina(Fato fatoCombinado) {

		if (fatoCombinado == null) return false;

		if (this.predicado.getPred().compareTo(fatoCombinado.getPredicado().getPred()) == 0 &&
				this.args.size() == fatoCombinado.args.size()) {

			for(int index = 0; index < fatoCombinado.getArgs().size(); index++){

				Termo termoAvaliando = this.getTermo(index);
				Termo termoCombinado = fatoCombinado.getTermo(index);

				boolean unificou = false;
				switch(termoAvaliando.getTipo()){
				case VALOR_INTEIRO:
					unificou = checarCompatibilidadeValorInteiro((ValorInteiro) termoAvaliando, termoCombinado);
					break;
				case ATOMO:
					unificou = checarCompatibilidadeAtomo((Atomo) termoAvaliando, termoCombinado);
					break;
				case VARIAVEL:
					continue;
				}

				if(!unificou){
					return false;
				}
			}

		}else{
			return false;
		}
		
		return true;

	}

	private boolean checarCompatibilidadeAtomo(Atomo termoAvaliando,
			Termo termoCombinado) {
		
		switch(termoCombinado.getTipo()){
			case VARIAVEL:
				return true;
			case VALOR_INTEIRO:
				return false;
			case ATOMO:
				Atomo combinado = (Atomo) termoCombinado;
				return (termoAvaliando.getValor().compareTo(combinado.getValor()) == 0);
		}
		
		return true;
	}

	private boolean checarCompatibilidadeValorInteiro(ValorInteiro termoAvaliando,
			Termo termoCombinado) {
		
		switch(termoCombinado.getTipo()){
			case VARIAVEL:
				return true;
			case VALOR_INTEIRO:
				ValorInteiro combinado = (ValorInteiro) termoCombinado;
				return (termoAvaliando.getValor() == combinado.getValor());
			case ATOMO:
				return false;
		}
		
		return true;
	}
	
	public Predicado getPredicado() {
		return predicado;
	}

	public void setPredicado(Predicado predicado) {
		this.predicado = predicado;
	}

	public ArrayList<Termo> getArgs() {
		return args;
	}

	public void setArgs(ArrayList<Termo> args) {
		this.args = args;
	}

	public Termo getTermo(int index){
		return args.get(index);
	}

	@Override
	public String toString() {
		return "Fato [predicado=" + predicado.getPred() + ", args=" + args + "]";
	}

}
